package com.zxy.supplier_system.server.service

import com.zxy.supplier_system.server.configuration.DatabaseInitializerHandler
import com.zxy.supplier_system.server.controller.EmployeeController
import com.zxy.supplier_system.server.entity.Employee
import com.zxy.supplier_system.server.entity.EmployeeRole
import com.zxy.supplier_system.server.entity.EmployeeSetting
import com.zxy.supplier_system.server.entity.Role
import com.zxy.supplier_system.server.repository.EmployeeRepository
import com.zxy.supplier_system.server.repository.SystemServiceRepository
import com.zxy.supplier_system.server.utils.IntIdAndName
import com.zxy.supplier_system.server.utils.orElse404
import com.zxy.supplier_system.server.utils.toIntIdAndName
import org.apache.commons.lang3.RandomStringUtils
import org.hibernate.Hibernate
import org.springframework.data.domain.AuditorAware
import org.springframework.data.domain.Page
import org.springframework.data.domain.Pageable
import org.springframework.data.repository.findByIdOrNull
import org.springframework.http.HttpStatus
import org.springframework.security.core.context.SecurityContextHolder
import org.springframework.security.core.userdetails.UserDetailsService
import org.springframework.security.core.userdetails.UsernameNotFoundException
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import org.springframework.web.server.ResponseStatusException
import java.util.*
import kotlin.jvm.optionals.getOrNull


@Service
class EmployeeService(
    private val employeeRepository: EmployeeRepository,
    private val employeeOperateRecordService: EmployeeOperateRecordService,
) : UserDetailsService, AuditorAware<Employee> {

    @Transactional(readOnly = true)
    override fun loadUserByUsername(username: String): Employee {
        val employee = this.employeeRepository.findById(username.toInt())
            .orElseThrow { UsernameNotFoundException("$username not found") }
        Hibernate.initialize(employee.roles)
        Hibernate.initialize(employee.setting)
        return employee
    }

    @Transactional(readOnly = true)
    fun queryEmployees(
        queryEmployeeRequest: EmployeeController.QueryEmployeeRequest,
        pageable: Pageable
    ): Page<EmployeeController.EmployeeResponse> {
        return this.employeeRepository.findAll(
            EmployeeRepository.Specifications.fromQueryEmployeeRequest(queryEmployeeRequest)
                .and(
                    SystemServiceRepository.Specifications.systemServiceEquals(
                        { root -> root },
                        getRequiredCurrentEmployee().systemService
                    )
                )
                .and(
                    EmployeeRepository.Specifications.roleNameNotEqual(DatabaseInitializerHandler.ROLE_SUPER_ADMIN_NAME)
                ), pageable
        ).map {
            EmployeeController.EmployeeResponse(
                it.id!!,
                it.name,
                it.phoneNumber,
                it.roles.map { employeeRole -> employeeRole.role.toIntIdAndName() },
                it.createdDateTime,
                it.enabled,
            )
        }
    }

    @Transactional
    fun createEmployee(createEmployeeRequest: EmployeeController.CreateEmployeeRequest): EmployeeController.CreateEmployeeResponse {
        val password =
            createEmployeeRequest.password ?: RandomStringUtils.randomAlphanumeric(Employee.INITIAL_PASSWORD_LENGTH)
        val employee = this.employeeRepository.saveAndFlush(Employee().apply {
            this.name = createEmployeeRequest.name
            this.phoneNumber = createEmployeeRequest.phoneNumber
            this.password = password
            this.systemService = getRequiredCurrentEmployee().systemService
        })
        employee.setting = EmployeeSetting().apply {
            this.id = employee.id
        }
        this.employeeRepository.save(employee)
        employee.roles.addAll(createEmployeeRequest.roleIds.map {
            EmployeeRole().apply {
                this.employee = employee
                this.role = Role().apply {
                    this.id = it
                }
            }
        })
        this.employeeRepository.save(employee)
        this.employeeOperateRecordService.createEmployee(employee)
        return EmployeeController.CreateEmployeeResponse(password)
    }


    @Transactional
    fun updateEmployee(
        id: Int,
        updateEmployeeRequest: EmployeeController.UpdateEmployeeRequest
    ) {
        val employee = this.employeeRepository.findByIdAndSystemService(id,getRequiredCurrentEmployee().systemService).orElse404()
        employee.name = updateEmployeeRequest.name
        employee.phoneNumber = updateEmployeeRequest.phoneNumber
        val roleIds = updateEmployeeRequest.roleIds
        if (employee.roles != roleIds) {
            employee.roles.clear()
            employee.roles.addAll(roleIds.map {
                EmployeeRole().apply {
                    this.employee = employee
                    this.role = Role().apply {
                        this.id = it
                    }
                }
            })
        }
        this.employeeRepository.save(employee)
        this.employeeOperateRecordService.updateEmployee(employee)
    }

    override fun getCurrentAuditor(): Optional<Employee> {
        val principal = getCurrentContextEmployee()
        return Optional.ofNullable(principal)
    }

    fun getEmployeeSummariesSortBySaleManager(): List<IntIdAndName> {
        return this.employeeRepository.findAll(
            EmployeeRepository.Specifications.orderBySaleManager().and(
                SystemServiceRepository.Specifications.systemServiceEquals(
                    { root -> root },
                    getRequiredCurrentEmployee().systemService
                )
            )
        ).map {
            IntIdAndName(it.id!!, it.name)
        }
    }

    fun getRequiredCurrentEmployee(): Employee {
        return currentAuditor.getOrNull() ?: throw ResponseStatusException(HttpStatus.UNAUTHORIZED)
    }

    @Transactional
    fun setPassword(updatePasswordRequest: EmployeeController.UpdatePasswordRequest) {
        val employee = this.getRequiredCurrentEmployee()
        employee.password = updatePasswordRequest.password
        this.employeeRepository.save(employee)
    }

    fun getAllSummaries(): List<IntIdAndName> {
        return this.employeeRepository.findAll(
            EmployeeRepository.Specifications.orderBySaleManager().and(
                SystemServiceRepository.Specifications.systemServiceEquals(
                    { root -> root },
                    getRequiredCurrentEmployee().systemService
                )
            )
        ).map {
            it.toIntIdAndName()
        }
    }

    companion object {
        fun getCurrentContextEmployee(): Employee? {
            val authentication = SecurityContextHolder.getContext().authentication
            val principal = authentication?.principal as? Employee
            return principal
        }
    }

    @Transactional(readOnly = true)
    fun getEmployeeDetail(id: Int): EmployeeController.EmployeeDetailResponse {
        val employee = this.employeeRepository.findByIdOrNull(id).orElse404()
        return EmployeeController.EmployeeDetailResponse(
            employee.id!!,
            employee.name,
            employee.phoneNumber,
            employee.roles.map {
                it.role
            }.map {
                it.toIntIdAndName()
            },
            employee.createdDateTime,
            employee.createdBy?.toIntIdAndName(),
        )
    }

    @Transactional
    fun updateSetting(request: EmployeeController.UpdateEmployeeSettingRequest) {
        val employee = this.getRequiredCurrentEmployee()
        employee.setting.apply {
            subscribeAllMarketOrderSubmittedWechatNotification = request.subscribeAllMarketOrderSubmittedWechatNotification
        }
        this.employeeRepository.save(employee)
    }

    @Transactional
    fun disableEmployee(id: Int) {
        val employee =
            this.employeeRepository.findByIdAndSystemService(id, getRequiredCurrentEmployee().systemService).orElse404()
        employee.enabled = false
        this.employeeRepository.save(employee)
        employeeOperateRecordService.disableEmployee(employee)
    }

    @Transactional
    fun enableEmployee(id: Int) {
        val employee = this.employeeRepository.findByIdAndSystemService(id, getRequiredCurrentEmployee().systemService).orElse404()
        employee.enabled = true
        this.employeeRepository.save(employee)
        employeeOperateRecordService.enableEmployee(employee)
    }
}